package model.sistema;

import static org.mockito.Mockito.*;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.*;

import java.util.HashSet;
import java.util.Set;

import model.cliente.Cliente;
import model.filtros.*;
import model.gastos.ConfiguracionGeneral;
import model.gastos.TEM;
import model.prestamos.Prestamo;
import model.prestamos.seguroDeVida.TipoDeSeguroDeVida;

import org.joda.time.LocalDate;
import org.junit.*;

import excepciones.prestamos.CantidadDeCuotasInvalidaException;
import excepciones.prestamos.MontoInvalidoDePrestamoException;

/**
 * Test suite de la clase Sistema.
 * @author Stefania, Emanuel
 */

public class SistemaTest {
	
	private Sistema sistema;
	private Cliente mockCliente;
	private Set<Prestamo> prestamosCliente;
	private Prestamo mockPrestamo1;
	private Prestamo mockPrestamo2;
	private double monto;
	private int cantCuotas;
	private ConfiguracionGeneral mockConfigInicial;
	private ConfiguracionGeneral mockConfig2;
	private TEM mockTEM;
	private TipoDeSeguroDeVida mockTipoDeSeguro;
	
	@Before
	public void setUp(){
		this.monto=20000;
		this.cantCuotas=12;
		
		this.mockTEM=mock(TEM.class);
		when(this.mockTEM.getPorcentaje()).thenReturn(0.002);
		
		this.mockConfigInicial=mock(ConfiguracionGeneral.class);
		when(this.mockConfigInicial.getTEMDeCuota(any(int.class))).thenReturn(this.mockTEM);
		when(this.mockConfigInicial.getGastosGlobalesDeOtorgamiento()).thenReturn(500.0);
		when(this.mockConfigInicial.getGastosAdministrativosMensuales()).thenReturn(20.0);
		when(this.mockConfigInicial.getGastoFijo()).thenReturn(100.0);
		when(this.mockConfigInicial.estaVigente()).thenReturn(true);
		
		this.mockConfig2=mock(ConfiguracionGeneral.class);
		
		this.mockPrestamo1=mock(Prestamo.class);
		this.mockPrestamo2=mock(Prestamo.class);
		
		this.prestamosCliente=new HashSet<Prestamo>();
		this.prestamosCliente.add(this.mockPrestamo1);
		this.prestamosCliente.add(this.mockPrestamo2);
		
		this.mockCliente=mock(Cliente.class);
		when(mockCliente.getPrestamos()).thenReturn(this.prestamosCliente);
		
		this.mockTipoDeSeguro=mock(TipoDeSeguroDeVida.class);
		
		this.sistema=new Sistema(mockConfigInicial);
		this.sistema.guardarCliente(mockCliente);
	}
	
	@Test
	public void testBusquedaDePrestamos(){
		FiltroDeBusqueda mockCriterio=mock(FiltroDeBusqueda.class);
		when(mockCriterio.coincide(this.mockPrestamo1)).thenReturn(true);
		when(mockCriterio.coincide(this.mockPrestamo2)).thenReturn(false);
		
		Set<Prestamo> resultados=this.sistema.buscarPrestamos(mockCriterio);
		
		assertTrue(resultados.contains(this.mockPrestamo1));
		assertFalse(resultados.contains(this.mockPrestamo2));
	}
	
	@Test
	public void testGetPosiblesDeudores(){
		when(this.mockPrestamo1.adeudaCuotas()).thenReturn(true);
		when(this.mockPrestamo1.esEnCurso()).thenReturn(true);
		when(this.mockPrestamo2.adeudaCuotas()).thenReturn(false);
		when(this.mockPrestamo2.esEnCurso()).thenReturn(false);
		
		Set<Prestamo> resultados=this.sistema.getPosiblesDeudores();
		
		assertTrue(resultados.contains(this.mockPrestamo1));
		assertFalse(resultados.contains(this.mockPrestamo2));
	}
	
	@Test
	public void testGetPosiblesDeudoresIncobrables(){
		when(this.mockPrestamo1.esEnDeuda()).thenReturn(true);
		when(this.mockPrestamo2.esEnDeuda()).thenReturn(false);
		
		Set<Prestamo> resultados=this.sistema.getPosiblesDeudoresIncobrables();
		
		assertTrue(resultados.contains(this.mockPrestamo1));
		assertFalse(resultados.contains(this.mockPrestamo2));
	}
	
	@Test
	public void testGetPendientesDeAprobacion(){
		when(this.mockPrestamo1.esSolicitado()).thenReturn(true);
		when(this.mockPrestamo2.esSolicitado()).thenReturn(false);
		
		Set<Prestamo> resultados=this.sistema.getPendienteDeAprobacion();
		
		assertTrue(resultados.contains(this.mockPrestamo1));
		assertFalse(resultados.contains(this.mockPrestamo2));
	}
	
	@Test
	public void testGetPrestamos(){
		Set<Prestamo> resultados=this.sistema.getPrestamos();
		
		assertTrue(resultados.contains(this.mockPrestamo1));
		assertTrue(resultados.contains(this.mockPrestamo2));
	}
	
	/**
	 * Verifica que el calculo de la cuota devuelve un valor.
	 * @throws MontoInvalidoDePrestamoException 
	 * @throws CantidadDeCuotasInvalidaException 
	 */
	@Test
	public void testGetValorCuotaAproximada() throws CantidadDeCuotasInvalidaException, MontoInvalidoDePrestamoException{
		double valorCuota = this.sistema.calcularCuotaAproximada(this.monto, this.cantCuotas);
		assertThat(valorCuota, instanceOf(Double.class));
	}
	
	/**
	 * Verifica que el prestamo preliminar no puede tener menos de 1 cuota.
	 * @throws MontoInvalidoDePrestamoException 
	 * @throws CantidadDeCuotasInvalidaException 
	 */
	@Test(expected=CantidadDeCuotasInvalidaException.class)
	public void testNoSePuedePedirMenosDeUnaCuota() throws CantidadDeCuotasInvalidaException, MontoInvalidoDePrestamoException{
		this.sistema.calcularCuotaAproximada(this.monto, 0);
		fail("Se debio levantar una excepcion!");
	}
	
	/**
	 * Verifica que el prestamo preliminar no puede ser de menos de $1.
	 * @throws MontoInvalidoDePrestamoException 
	 * @throws CantidadDeCuotasInvalidaException 
	 */
	@Test(expected=MontoInvalidoDePrestamoException.class)
	public void testNoSePuedePedirPrestamoMenorAUnPeso() throws CantidadDeCuotasInvalidaException, MontoInvalidoDePrestamoException{
		this.sistema.calcularCuotaAproximada(0, this.cantCuotas);
		fail("Se debio levantar una excepcion!");
	}
	
	@Test
	public void testNuevoPrestamoParaCliente() throws CantidadDeCuotasInvalidaException, MontoInvalidoDePrestamoException{
		this.sistema.nuevoPrestamoPara(this.mockCliente, this.monto, this.cantCuotas, this.mockTipoDeSeguro);
		verify(this.mockCliente).agregarPrestamo(any(Prestamo.class));
		verify(this.mockCliente).updateTipo();
	}
	
	@Test
	public void testAgregarConfiguracionGeneralTerminaLaAnterior(){
		LocalDate hoy=new LocalDate();
		when(this.mockConfig2.getFechaInicio()).thenReturn(hoy);
		this.sistema.cargarConfiguracionGeneral(this.mockConfig2);
		when(this.mockConfig2.estaVigente()).thenReturn(true);
		when(this.mockConfigInicial.estaVigente()).thenReturn(false);
		verify(this.mockConfigInicial).finalizar(hoy.minusDays(1));
		assertSame(this.sistema.getConfiguracionActual(),this.mockConfig2);
	}


}
